<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <title>资源监控</title>
  <link rel="stylesheet" href="css/style.css">
  <!-- Vue 3 & Element Plus -->
  <script src="./public/vue.global.prod.js"></script>
  <!-- 引入 Element Plus -->
  <link rel="stylesheet" href="./public/element-plus-index.css">
  <script src="./public/index.full.js"></script>
  <link rel="stylesheet" href="./public/icon.css" />
  <!-- ECharts -->
  <script src="./public/echarts.min.js"></script>
  <script src="./public/axios.min.js"></script>
  <style>
    .breadcrumb {
      margin-bottom: 30px;
    }

    .overview {
      display: flex;
      gap: 16px;
      margin-bottom: 24px;
    }

    .overview__card {
      flex: 1;
      min-width: 220px;
    }

    .overview__content {
      background: #fafbfc;
      border-radius: 4px;
      padding: 24px 0;
      min-height: 100px;
      margin: 0 8px 0 8px;
    }

    .overview__content--row {
      display: flex;
      flex-direction: row;
      justify-content: space-between;
      align-items: center;
    }

    .overview__col {
      display: flex;
      flex-direction: column;
      align-items: center;
      flex: 1;
    }

    .overview__desc {
      color: #888;
      font-size: 14px;
      margin-bottom: 4px;
    }

    .overview__num {
      font-size: 22px;
      font-weight: bold;
      margin-top: 2px;
    }

    .overview__num--warn {
      color: #faad14;
    }

    .overview__num--danger {
      color: #f5222d;
    }

    .overview__content--grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      row-gap: 12px;
      column-gap: 24px;
      justify-items: start;
      align-items: center;
    }

    .overview__item {
      display: flex;
      align-items: center;
      gap: 4px;
    }

    .overview__dot {
      display: inline-block;
      width: 8px;
      height: 8px;
      background: #faad14;
      border-radius: 50%;
      margin-right: 4px;
    }

    .monitor-section {
      margin-top: 0;
    }

    .monitor-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 8px;
    }

    .monitor-filters {
      display: flex;
      gap: 8px;
      align-items: center;
    }

    .node-list {
      margin: 12px 0;
    }

    .node-card {
      display: flex;
      align-items: center;
      border: 1.5px solid #b7eb8f;
      border-radius: 6px;
      padding: 12px 16px;
      margin-bottom: 8px;
      gap: 16px;
      background: #e8f5e9;
      cursor: pointer;
      transition: border-color 0.2s, background 0.2s;
    }

    .node-card.selected {
      border-color: #52c41a !important;
      background: #f6ffed !important;
    }

    .node-checkbox {
      margin-right: 8px;
    }

    .node-info {
      flex: 1;
    }

    .node-title {
      font-weight: bold;
      font-size: 16px;
    }

    .node-ip {
      color: #888;
      font-size: 13px;
      margin-bottom: 4px;
    }

    .node-stats {
      display: flex;
      gap: 16px;
      font-size: 13px;
      align-items: center;
    }

    .node-stat-icon {
      margin-right: 2px;
      font-size: 16px;
      vertical-align: middle;
    }

    .actions {
      margin-top: 12px;
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .el-pagination {
      margin-top: 12px;
      text-align: right;
    }

    .charts-row {
      display: flex;
      flex-wrap: wrap;
      /* 关键：自动换行 */
      gap: 16px;
      margin: 16px 0 0 0;
    }

    .chart-card {
  flex: 0 0 240px;
  min-width: 220px;
  max-width: 300px;
  height: 240px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  overflow: hidden;
}

    .chart-title {
      font-size: 14px;
      font-weight: bold;
      margin-bottom: 4px;
      text-align: center;
    }

    .disk-chart-info {
      font-size: 12px;
      color: #666;
      text-align: center;
      margin-top: 2px;
    }

    .overview__content--grid2 {
      display: grid;
      grid-template-columns: 1fr auto;
      row-gap: 12px;
      column-gap: 12px;
      align-items: center;
    }

    .overview__item {
      display: flex;
      align-items: center;
      gap: 4px;
    }

    .overview__num {
      font-size: 22px;
      font-weight: bold;
      justify-self: end;
    }

    .overview__content--warn-grid {
      display: flex;
      flex-direction: row;
      justify-content: flex-start;
      align-items: flex-start;
      gap: 40px;
      /* 两列间距 */
      background: #fafbfc;
      border-radius: 4px;
      padding: 24px 0 24px 24px;
      min-height: 100px;
    }

    .overview__warn-col {
      display: flex;
      flex-direction: column;
      gap: 12px;
    }

    .overview__warn-item {
      display: flex;
      align-items: center;
      gap: 6px;
    }

    .overview__dot {
      display: inline-block;
      width: 8px;
      height: 8px;
      background: #faad14;
      border-radius: 50%;
      margin-right: 4px;
    }

    .overview__desc {
      color: #888;
      font-size: 14px;
    }

    .overview__num {
      font-size: 18px;
      font-weight: bold;
      margin-left: 4px;
    }

    .overview__num--warn {
      color: #222;
    }
  </style>
</head>

<body>
  <div id="app" class="container">
    <!-- 面包屑 -->
    <el-breadcrumb class="breadcrumb" separator="/">
      <el-breadcrumb-item>首页</el-breadcrumb-item>
      <el-breadcrumb-item>资源监控</el-breadcrumb-item>
    </el-breadcrumb>
    <!-- 概览卡片 -->
    <div class="overview">
      <!-- 第一张卡片 -->
      <el-card class="overview__card">
        <template #header>
          <b>GPU Server资源概览</b>
        </template>
        <div class="overview__content overview__content--row">
          <div class="overview__col">
            <div class="overview__desc">GPU节点数</div>
            <div class="overview__num">{{ overview.gpuNodes }}</div>
          </div>
          <div class="overview__col">
            <div class="overview__desc">总并发数</div>
            <div class="overview__num">{{ overview.totalConcurrency }}</div>
          </div>
        </div>
      </el-card>
      <!-- 第二张卡片 -->
      <el-card class="overview__card">
        <template #header>
          <b>GPU Server故障预警</b>
        </template>
        <div class="overview__content overview__content--row">
          <div class="overview__col">
            <div class="overview__desc">离线节点</div>
            <div class="overview__num overview__num--danger">{{ overview.offlineNodes }}</div>
          </div>
          <div class="overview__col">
            <div class="overview__desc">抖动节点</div>
            <div class="overview__num overview__num--warn">{{ overview.fluctuatingNodes }}</div>
          </div>
        </div>
      </el-card>
      <!-- 第三张卡片 -->
      <el-card class="overview__card">
        <template #header>
          <b>GPU Server资源预警</b>
        </template>
        <div class="overview__content overview__content--warn-grid">
          <div class="overview__warn-col">
            <div class="overview__warn-item">
              <span class="overview__dot"></span>
              <span class="overview__desc">CPU负载过高</span>
              <span class="overview__num overview__num--warn">{{ overview.cpuWarning }}</span>
            </div>
            <div class="overview__warn-item">
              <span class="overview__dot"></span>
              <span class="overview__desc">内存负载过高</span>
              <span class="overview__num overview__num--warn">{{ overview.memWarning }}</span>
            </div>
          </div>
          <div class="overview__warn-col">
            <div class="overview__warn-item">
              <span class="overview__dot"></span>
              <span class="overview__desc">GPU负载过高</span>
              <span class="overview__num overview__num--warn">{{ overview.gpuWarning }}</span>
            </div>
            <div class="overview__warn-item">
              <span class="overview__dot"></span>
              <span class="overview__desc">磁盘负载过高</span>
              <span class="overview__num overview__num--warn">{{ overview.diskWarning }}</span>
            </div>
          </div>
        </div>
      </el-card>
    </div>
    <!-- 监控列表 -->
    <el-card class="monitor-section">
      <div class="monitor-header">
        <span style="font-weight:bold;">GPU Server资源监控一览</span>
        <div class="monitor-filters">
          <el-input size="small" placeholder="区域ID" style="width:100px"></el-input>
          <el-input size="small" placeholder="IP" style="width:120px"></el-input>
          <el-input size="small" placeholder="渲染节点名称" style="width:140px"></el-input>
          <span>刷新频率：</span>
          <el-select v-model="refreshRate" size="small" style="width:80px">
            <el-option label="5s" value="5"></el-option>
            <el-option label="10s" value="10"></el-option>
            <el-option label="30s" value="30"></el-option>
          </el-select>
          <el-button icon="el-icon-refresh" size="small" circle @click="refresh"></el-button>
        </div>
      </div>
      <el-tabs v-model="activeTab">
        <el-tab-pane :label="`在线节点(${nodes.length})`" name="online">
          <div class="node-list">
            <div v-for="node in nodes" :key="node.id" class="node-card"
              :class="{selected: selectedNode && selectedNode.id === node.id}" @click="selectNode(node)">
              <el-checkbox class="node-checkbox" v-model="node.selected" @click.stop></el-checkbox>
              <div class="node-info">
                <div class="node-title">{{ node.name }}</div>
                <div class="node-ip">{{ node.ip }}</div>
                <div class="node-stats">
                  <span>
                    <el-icon class="node-stat-icon"><i class="el-icon-cpu"></i></el-icon>
                    CPU {{ node.cpu }}
                  </span>
                  <span>
                    <el-icon class="node-stat-icon"><i class="el-icon-monitor"></i></el-icon>
                    显存 {{ node.gpuMem }}
                  </span>
                  <span>
                    <el-icon class="node-stat-icon"><i class="el-icon-folder"></i></el-icon>
                    磁盘 {{ node.disk }}
                  </span>
                  <span>
                    <el-icon class="node-stat-icon"><i class="el-icon-data-analysis"></i></el-icon>
                    内存 {{ node.mem }}
                  </span>
                </div>
              </div>
              <el-button icon="el-icon-refresh" size="small" circle title="重启" @click.stop></el-button>
            </div>
          </div>
          <!-- 详情图表区 -->
          <div v-if="selectedNode" class="charts-row">
            <el-card class="chart-card">
              <div class="chart-title">CPU使用率</div>
              <div id="cpuChart" style="width:120px;height:120px;margin:auto;"></div>
            </el-card>
            <el-card class="chart-card">
              <div class="chart-title">GPU使用率</div>
              <div id="gpuChart" style="width:120px;height:120px;margin:auto;"></div>
            </el-card>
            <el-card class="chart-card">
              <div class="chart-title">内存使用率</div>
              <div id="memChart" style="width:120px;height:120px;margin:auto;"></div>
            </el-card>
            <el-card v-for="disk in selectedNode.diskDetail" :key="disk.name" class="chart-card">
              <div class="chart-title">{{ disk.name }} 使用 {{ disk.usedSpace }}/{{ disk.totalSpace }}</div>
              <div :id="'diskChart_' + disk.name" style="width:120px;height:120px;margin:auto;"></div>
              <div class="disk-chart-info">{{ disk.usagePercentage }}</div>
            </el-card>
          </div>
        </el-tab-pane>
        <el-tab-pane label="冻结节点(0)" name="frozen"></el-tab-pane>
        <el-tab-pane label="抖动节点(0)" name="fluctuating"></el-tab-pane>
        <el-tab-pane label="离线节点(0)" name="offline"></el-tab-pane>
        <el-tab-pane label="离线冻结节点(0)" name="offlineFrozen"></el-tab-pane>
      </el-tabs>
      <div class="actions">
        <el-checkbox v-model="allSelected" @change="toggleAll">全选</el-checkbox>
        <el-button type="primary" size="small">冻结</el-button>
        <el-button type="primary" size="small">重启</el-button>
      </div>
      <el-pagination layout="total, prev, pager, next" :total="nodes.length" :page-size="10" :current-page="1"
        style="margin-top:12px; text-align:right;" disabled />
    </el-card>
  </div>
  <script>
    const { createApp } = Vue
    createApp({
      data () {
        return {
          overview: {
            gpuNodes: 0,
            totalConcurrency: 0,
            offlineNodes: 0,
            fluctuatingNodes: 0,
            cpuWarning: 0,
            memWarning: 0,
            gpuWarning: 0,
            diskWarning: 0,
          },
          activeTab: 'online',
          nodes: [],
          allSelected: false,
          refreshRate: '5',
          selectedNode: null
        }
      },
      methods: {
        toggleAll (val) {
          this.nodes.forEach(n => n.selected = val)
        },
        refresh () {
          this.fetchResource()
        },
        getDonutColor (percent) {
          if (percent < 50) return '#52c41a'      // 绿色
          if (percent < 80) return '#1890ff'      // 蓝色
          if (percent < 90) return '#faad14'      // 黄色
          return '#f5222d'                        // 红色
        },
        fetchResource () {
          const token = sessionStorage.getItem('token')
          axios.get('/resource', {
          })
            .then(res => {
              const response = res.data
              if (response.status === "OK") {
                this.overview = {
                  gpuNodes: 1,
                  totalConcurrency: 0,
                  offlineNodes: 0,
                  fluctuatingNodes: 0,
                  cpuWarning: parseFloat(response.entity.cpu.usagePercentage) > 80 ? 1 : 0,
                  memWarning: parseFloat(response.entity.memory.usagePercentage) > 80 ? 1 : 0,
                  gpuWarning: parseFloat(response.entity.graphicCard[0].memoryUsage) > 80 ? 1 : 0,
                  diskWarning: parseFloat(response.entity.disk.usagePercentage) > 80 ? 1 : 0,
                }
                this.nodes = [
                  {
                    id: 1,
                    name: response.entity.graphicCard[0].gpuName,
                    ip: response.entity.ip,
                    cpu: response.entity.cpu.usagePercentage,
                    cpuCores: response.entity.cpu.cpuCores,
                    gpuMem: response.entity.graphicCard[0].memoryUsage,
                    gpuTotalMemory: response.entity.graphicCard[0].gpuTotalMemory,
                    gpuUsedMemory: response.entity.graphicCard[0].usedMemory,
                    disk: response.entity.disk.usagePercentage,
                    diskDetail: response.entity.disk.disks,
                    diskTotal: response.entity.disk.totalSpace,
                    diskUsed: response.entity.disk.usedSpace,
                    mem: response.entity.memory.usagePercentage,
                    memTotal: response.entity.memory.totalMemory,
                    memUsed: response.entity.memory.usedMemory,
                    selected: false
                  }
                ]
              } else {
                if (window.ElMessage) ElMessage.error("获取资源失败: " + (response.msg || '未知错误'))
              }
            })
            .catch(() => {
              if (window.ElMessage) ElMessage.error("网络错误，请重试")
            })
        },
        selectNode (node) {
          if (this.selectedNode && this.selectedNode.id === node.id) {
            this.selectedNode = null
          } else {
            this.selectedNode = node
            this.$nextTick(() => {
              this.renderCharts()
            })
          }
        },
        renderCharts () {
          // 获取接口数据
          const cpuPercent = parseFloat(this.selectedNode.cpu)
          const gpuPercent = parseFloat(this.selectedNode.gpuMem)
          const memPercent = parseFloat(this.selectedNode.mem)

          // 环形图
          const getColor = this.getDonutColor

          function renderDonutChart (domId, percent, color) {
            const chartDom = document.getElementById(domId)
            if (chartDom) {
              const chart = echarts.init(chartDom)
              chart.setOption({
                series: [{
                  type: 'pie',
                  radius: ['70%', '90%'],
                  avoidLabelOverlap: false,
                  label: { show: false },
                  labelLine: { show: false },
                  data: [
                    { value: percent, name: '已用', itemStyle: { color: color } },
                    { value: 100 - percent, name: '可用', itemStyle: { color: '#f0f0f0' } }
                  ]
                }],
                graphic: {
                  type: 'text',
                  left: 'center',
                  top: 'center',
                  style: {
                    text: percent + '%',
                    fontSize: 20,
                    fontWeight: 'bold',
                    fill: color
                  }
                }
              })
            }
          }
          // 渲染CPU、GPU、内存
          renderDonutChart('cpuChart', cpuPercent, getColor(cpuPercent))
          renderDonutChart('gpuChart', gpuPercent, getColor(gpuPercent))
          renderDonutChart('memChart', memPercent, getColor(memPercent))

          // 磁盘环形图（每个分区）
          if (this.selectedNode && this.selectedNode.diskDetail) {
            this.selectedNode.diskDetail.forEach(disk => {
              const percent = parseFloat(disk.usagePercentage)
              const chartId = 'diskChart_' + disk.name
              const color = getColor(percent)
              const chartDom = document.getElementById(chartId)
              if (chartDom) {
                const diskChart = echarts.init(chartDom)
                diskChart.setOption({
                  series: [{
                    type: 'pie',
                    radius: ['70%', '90%'],
                    avoidLabelOverlap: false,
                    label: { show: false },
                    labelLine: { show: false },
                    data: [
                      { value: percent, name: '已用', itemStyle: { color: color } },
                      { value: 100 - percent, name: '可用', itemStyle: { color: '#f0f0f0' } }
                    ]
                  }],
                  graphic: {
                    type: 'text',
                    left: 'center',
                    top: 'center',
                    style: {
                      text: disk.usagePercentage,
                      fontSize: 20,
                      fontWeight: 'bold',
                      fill: color
                    }
                  }
                })
              }
            })
          }
        }
      },
      mounted () {
        this.fetchResource()
      }
    }).use(ElementPlus).mount('#app');
  </script>
</body>

</html>